#include <linux/autoconf.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>

#include <linux/kernel.h>       /* printk() */
#include <linux/slab.h>         /* kmalloc() */
#include <linux/fs.h>           /* everything... */
#include <linux/errno.h>        /* error codes */
#include <linux/types.h>        /* size_t */
#include <linux/proc_fs.h>
#include <linux/fcntl.h>        /* O_ACCMODE */
#include <linux/seq_file.h>
#include <linux/cdev.h>

#include <asm/system.h>         /* cli(), *_flags */
#include <asm/uaccess.h>        /* copy_*_user */

#include "jchar.h"//fixme: improve !  useexternal *.h file for store gloval constants


int jchar_major = 0;
int jchar_minor = 0;
int jchar_nr_devs = 4;
int jchar_quantum = 4000;
int jchar_qset = 1000;


struct jchar_dev *jchar_devices; /* allocated in jchar_init_module */



// Follow the list
/*!
 * @param dev pointer to jchar device structure
 * @param n number of jchar_dev structures that will be walk.
 * @param pointer to the last jchar_qset structure after the walk.
 **/
struct jchar_qset *jchar_follow(struct jchar_dev *dev, int n)
{
        struct jchar_qset *qs = dev->data;

        /* Allocate first qset explicitly if need be */
        if (! qs) {
                qs = dev->data = kmalloc(sizeof(struct jchar_qset), GFP_KERNEL);
                if (qs == NULL)
                        return NULL;  /* Never mind */
                memset(qs, 0, sizeof(struct jchar_qset));
        }

        /* Then follow the list */
        while (n--) {
                if (!qs->next) {
                        qs->next = kmalloc(sizeof(struct jchar_qset), GFP_KERNEL);
                        if (qs->next == NULL)
                                return NULL;  /* Never mind */
                        memset(qs->next, 0, sizeof(struct jchar_qset));
                }
                qs = qs->next;
                continue;
        }
        return qs;
}



int jchar_trim(struct jchar_dev *dev)
{
  struct jchar_qset *next, *dptr;
  int qset = dev->qset; /* "dev" is not null */
  int i;
  for (dptr=dev->data; dptr; dptr = next)
  {
    if (dptr->data)
    {
      for (i=0; i<qset; i++)
      {  kfree(dptr->data[i]); }
      kfree(dptr->data);
      dptr->data = NULL;
    }
    next = dptr->next;
    kfree(dptr);
  }
  dev->size=0;
  dev->quantum = jchar_quantum;
  dev->qset = jchar_qset;
  dev->data=NULL;
  return 0;
}



/// Open file operation.
/*!
  * @param inode pointer.
  * @param filp file structure pointer.
  * @return 0 if success.
**/
int jchar_open(struct inode *inode, struct file *filp)
{
  struct jchar_dev *dev;
  dev = container_of(inode->i_cdev,struct jchar_dev, cdev);
  filp->private_data = dev; /* for future use of other methods */

  if ( (filp->f_flags & O_ACCMODE) == O_WRONLY  )
  {
    jchar_trim(dev); //*ignore errors*
  }
  return 0;
}


/// Release file operation
/*!
 *  @param inode pointer.
 *  @param filp file structure pointer.
 *  @return 0 if success.
 **/
int jchar_release(struct inode *inode, struct file *filp)
{
  return 0;
}


/// Read file operation
/*!
 *  @param filp file structure pointer
 *  @param buf pointer the to the user space buffer that have the data to be read
 *  @param count size of the user space buffer
 *  @param f_pos pointer to the current file position after the system call.
 **/
ssize_t jchar_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
#ifdef JCHAR_DEBUG
printk(KERN_WARNING "jchar: read file operation called \n");
#endif

  struct jchar_dev *dev = filp->private_data;
  struct jchar_qset *dptr; //the first listitem
  int quantum = dev->quantum, qset = dev->qset;
  int itemsize = quantum * qset; /* how many bytes in the listitem */
  int item, s_pos, q_pos, rest;
  ssize_t retval = 0;
  if (down_interruptible(&dev->sem))
    return -ERESTARTSYS;
  if (*f_pos >= dev->size)
    goto out;
  if (*f_pos + count > dev->size)
    count = dev->size - *f_pos;

#ifdef JCHAR_DEBUG
printk(KERN_WARNING "jchar: read, find listitem, qset, index, and offset \n");
#endif

  /* find listitem, qset index, and offset in the quantum */
  item = (long)*f_pos / itemsize;
  rest = (long)*f_pos % itemsize;
  s_pos = rest / quantum; q_pos = rest % quantum;


#ifdef JCHAR_DEBUG
printk(KERN_WARNING "jchar: moving forward [jchar_follow] \n");
#endif
  /* follow the list up to the right position (defined elsewhere) */
  dptr = jchar_follow(dev, item); //revisar funcionamiento

  if (dptr == NULL || ! dptr->data || ! dptr->data[s_pos])
    goto out; // don't fill holes

  /* read only up to the end of the quantum */
  if (count > quantum - q_pos)
    count = quantum - q_pos;

#ifdef JCHAR_DEBUG
printk(KERN_WARNING "jchar: copying to the buffer for read \n");
#endif

  if (copy_to_user(buf,dptr->data[s_pos] + q_pos,count))
  {
    retval = -EFAULT;
    goto out;
  }

  *f_pos += count;
  retval = count;

 out:
#ifdef JCHAR_DEBUG
printk(KERN_WARNING "jchar: out called from read function, exiting... \n");
#endif
  up(&dev->sem);
  return retval;
}





/// Write file operation
/*!
 *  @param filp file structure pointer
 *  @param buf pointer the to the user space buffer that have the data to be write
 *  @param count size of the user space buffer
 *  @param f_pos pointer to the current file position after the system call.
 **/
ssize_t jchar_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
{
#ifdef JCHAR_DEBUG
printk(KERN_WARNING "jchar: write file operation called \n");
#endif


  struct jchar_dev *dev = filp->private_data;
  struct jchar_qset *dptr;
  int quantum = dev->quantum, qset = dev->qset;
  int itemsize = quantum * qset;
  int item, s_pos, q_pos, rest;
  ssize_t retval = -ENOMEM; /* value used in "goto out" statements*/

  if (down_interruptible(&dev->sem))
    return -ERESTARTSYS;

#ifdef JCHAR_DEBUG
printk(KERN_WARNING "jchar: write, finding listitem, qset, index and offset \n");
#endif
  /* find listitem, qset index and offset in the quantum */
  item = (long)*f_pos / itemsize;
  rest = (long)*f_pos % itemsize;
  s_pos = rest / quantum; q_pos = rest % quantum;

#ifdef JCHAR_DEBUG
printk(KERN_WARNING "jchar: moving forward to the right position [jchar_follow]\n");
#endif

  /*follow the list up to the righ position */
  dptr = jchar_follow(dev, item);
  if (dptr == NULL)
    goto out;
  if (!dptr->data)
  {
    dptr->data = kmalloc(qset*sizeof(char*),GFP_KERNEL);
    if (!dptr->data)
      goto out;
    memset(dptr->data,0,qset*sizeof(char *));
  }

  if (!dptr->data[s_pos])
  {
    dptr->data[s_pos] = kmalloc(quantum, GFP_KERNEL);
    if (!dptr->data[s_pos])
      goto out;
  }

  /* write only up to the end of this quantum */
  if (count > quantum - q_pos)
    count = quantum - q_pos;

#ifdef JCHAR_DEBUG
printk(KERN_WARNING "jchar: writing data in the buffer \n");
#endif

  if (copy_from_user(dptr->data[s_pos]+q_pos,buf,count)){
    retval = -EFAULT;
    goto out;
  }

  *f_pos += count;
  retval = count;

  /*update the size*/
  if (dev->size < *f_pos){
    dev->size = *f_pos;
  }

 out:
#ifdef JCHAR_DEBUG
printk(KERN_WARNING "jchar: out called from write function, exiting .... \n");
#endif
  up(&dev->sem);
  return retval;

}



// jchar device structure
struct file_operations jchar_fops = {
  .owner   = THIS_MODULE,
  //  .llseek  = jchar_llseek,
  .read    = jchar_read,
  .write   = jchar_write,
  //  .ioctl   = jchar_ioctl,
  .open    = jchar_open,
  .release = jchar_release,
  };


// Initialize kernel's cdev structure, the interface between our device (struct jchar_dev) and the kenel.
/*!
 * @param dev pointer to jchar device structure
 * @param index of the particular device
 **/
static void jchar_setup_cdev(struct jchar_dev *dev, int index)
{
  int err, devno = MKDEV(jchar_major, jchar_minor + index);

  cdev_init(&dev->cdev, &jchar_fops);
  dev->cdev.owner = THIS_MODULE;
  dev->cdev.ops = &jchar_fops;
  err = cdev_add(&dev->cdev, devno, 1);
  if (err){ printk(KERN_NOTICE "Error %d adding jchar%d", err, index); }
}


//exit function, do a cleanup.
void jchar_cleanup_module(void)
{
  int i;
  dev_t devno = MKDEV(jchar_major, jchar_minor);

#ifdef JCHAR_DEBUG
printk(KERN_WARNING "jchar: unregister major number and devices \n");
#endif

  /* Get rid of our char dev entries */
  if (jchar_devices) {
          for (i = 0; i < jchar_nr_devs; i++) {
                  jchar_trim(jchar_devices + i);
                  cdev_del(&jchar_devices[i].cdev);
          }
          kfree(jchar_devices);
  }

  unregister_chrdev_region(devno, jchar_nr_devs);
}

int jchar_init_module(void)
{
  int result, i;
  dev_t dev = 0;
  // Forcing dynamic allocation for major and minor numbers.
  #ifdef JCHAR_DEBUG
  printk(KERN_WARNING "jchar: forcing alloctaion of major number\n");
  #endif
  result = alloc_chrdev_region(&dev, jchar_minor, jchar_nr_devs, "jchar");
  jchar_major = MAJOR(dev);

  if (result < 0)
  {
    printk(KERN_WARNING "jchar: can't get major %d\n", jchar_major);
    return result;
  }

#ifdef JCHAR_DEBUG
printk(KERN_WARNING "jchar: new major number ->  %d\n", jchar_major);
#endif

#ifdef JCHAR_DEBUG
printk(KERN_WARNING "jchar: allocating memory for devices [jchar_nr_devs * sizeof(struct jchar_dev)]\n");
#endif
  // we need to do it dynamic because the number of devices can be specified at load time
  jchar_devices = kmalloc( jchar_nr_devs * sizeof(struct jchar_dev), GFP_KERNEL );
  if (!jchar_devices)
  {
#ifdef JCHAR_DEBUG
printk(KERN_WARNING "jchar: err, allocating memory for devices failed\n");
#endif
    result = -ENOMEM;
    goto fail;
  }

#ifdef JCHAR_DEBUG
printk(KERN_WARNING "jchar: writing zeros on devices structures\n");
#endif
  memset(jchar_devices, 0, jchar_nr_devs * sizeof(struct jchar_dev));


#ifdef JCHAR_DEBUG
printk(KERN_WARNING "jchar: initializing each device\n");
#endif
  /* Initialize each device. */
  for (i = 0; i < jchar_nr_devs; i++)
  {
    jchar_devices[i].quantum = jchar_quantum;
    jchar_devices[i].qset = jchar_qset;
    init_MUTEX(&jchar_devices[i].sem);
    jchar_setup_cdev(&jchar_devices[i], i);
  }

  return 0; /* succeed */

  fail:
        jchar_cleanup_module();
        return result;
}


module_init(jchar_init_module);
module_exit(jchar_cleanup_module);
